﻿using System;
using System.Collections.Generic;
using ProtoGame.GameLib;
using ProtoGame.RegioesGatilho;

namespace ProtoGame.Fases.Labirinto
{
    class PGArvoreLabirinto
    {
        enum TiposSaidas
        {
            Volta,
            Proximo1,
            Proximo2
        }

        const int NumOrdem = 2;
        static Random random = new Random();

        class Nodo
        {
            public List<Nodo> Filhos { get; private set; }

            public Nodo Pai { get; set; }

            public EntityManager SalaLabirinto { get; set; }

            public int QtdeFilhos
            {
                get { return Filhos.Count; }
            }

            public Nodo()
            {
                this.Pai = null;
                this.SalaLabirinto = null;
                Filhos = new List<Nodo>(NumOrdem);
            }

            public void AdicionarFilho(Nodo filho)
            {
                filho.Pai = this;
                Filhos.Add(filho);
            }

            public void EmbaralharFilhos(int trocas)
            {
                for (int i = 0; i < trocas; i++)
                {
                    int indice1 = random.Next(Filhos.Count);
                    int indice2 = random.Next(Filhos.Count);
                    
                    Nodo swap = Filhos[indice1];
                    Filhos[indice1] = Filhos[indice2];
                    Filhos[indice2] = swap;
                }
            }
        }

        PGLabirintos<TiposSaidas> construtor;
        Nodo arvore;

        public event EventHandler CompletouLabirinto;

        public void OnCompletouLabirinto(object sender, EventArgs e)
        {
            if (CompletouLabirinto != null)
                CompletouLabirinto(sender, e);
        }

        public EntityManager Atual
        {
            get { return arvore.SalaLabirinto; }
        }

        public PGArvoreLabirinto(uint altura, EventHandler dialogos_handler, Microsoft.Xna.Framework.Game game)
        {
            TiposSaidas[] listaProximos =  { TiposSaidas.Proximo1, TiposSaidas.Proximo2 };
            this.construtor = new PGLabirintos<TiposSaidas>(altura, TiposSaidas.Volta, listaProximos, game);

            arvore = GerarLabirinto(altura, dialogos_handler);
            arvore.SalaLabirinto.WakeUp();
        }

        Nodo GerarLabirinto(uint altura, EventHandler dialogos_handler)
        {
            Nodo inicio = new Nodo();
            inicio.SalaLabirinto = construtor.ConstruirLabirintoInicio(altura, escadas_handler, dialogos_handler);
            inicio.SalaLabirinto.Initialize();
            inicio.AdicionarFilho(CriarArvoreSolucao(altura - 1, dialogos_handler));

            return inicio;
        }

        Nodo CriarArvoreSolucao(uint altura, EventHandler dialogos_handler)
        {
            Nodo galho = new Nodo();

            if (altura > 1)
            {
                Nodo folhaPrincipal = CriarArvoreSolucao(altura - 1, dialogos_handler);
                galho.AdicionarFilho(folhaPrincipal);

                int numFolhasRestantes = random.Next(NumOrdem);
                for (int i = 0; i < numFolhasRestantes; i++)
                {
                    Nodo folha = CriarArvoreRecursivamente(altura - 1, dialogos_handler);
                    galho.AdicionarFilho(folha);
                }

                galho.SalaLabirinto = selecionarSalaLabirinto(altura, dialogos_handler,galho.QtdeFilhos);
                galho.SalaLabirinto.Initialize();

                galho.EmbaralharFilhos(galho.QtdeFilhos * 2);
            }
            else
            {
                galho.SalaLabirinto = construtor.ConstruirLabirintoFim(altura, escadas_handler, dialogos_handler);
                galho.SalaLabirinto.Initialize();
            }

            return galho;
        }

        Nodo CriarArvoreRecursivamente(uint altura, EventHandler dialogos_handler)
        {
            Nodo galho = new Nodo();

            if (altura > 1)
            {
                int numFolhas = random.Next(NumOrdem + 1);

                for (int i = 0; i < numFolhas; i++)
                {
                    Nodo folha = CriarArvoreRecursivamente(altura - 1, dialogos_handler);
                    galho.AdicionarFilho(folha);
                }

                galho.SalaLabirinto = selecionarSalaLabirinto(altura, dialogos_handler,galho.QtdeFilhos);
                galho.SalaLabirinto.Initialize();

                galho.EmbaralharFilhos(galho.QtdeFilhos * 2);
            }
            else
            {
                galho.SalaLabirinto = construtor.ConstruirLabirintoSemFim(altura, escadas_handler, dialogos_handler);
                galho.SalaLabirinto.Initialize();
            }

            return galho;
        }

        EntityManager selecionarSalaLabirinto(uint altura, EventHandler dialogos_handler, int qtdeFilhos)
        {
            EntityManager sala;

            switch (qtdeFilhos)
            {
                case 2:
                    sala = construtor.ConstruirLabirintoTwoWay(altura, escadas_handler, dialogos_handler);
                    break;

                case 1:
                    sala = construtor.ConstruirLabirintoOneWay(altura, escadas_handler, dialogos_handler);
                    break;

                case 0:
                    sala = construtor.ConstruirLabirintoSemFim(altura, escadas_handler, dialogos_handler);
                    break;

                default:
                    throw new NotImplementedException();
                //break;
            }

            return sala;
        }

        void escadas_handler(object sender, MudaMapaEventArgs<TiposSaidas> e)
        {
            switch (e.Id)
            {
                case TiposSaidas.Volta:
                    arvore = arvore.Pai;
                    break;

                case TiposSaidas.Proximo1:
                    if (arvore.Filhos.Count > 0)
                        arvore = arvore.Filhos[0];
                    else
                    {
                        /*gj = sender as PGGatilhoJogador;
                        gj.Alive = false;*/
                        OnCompletouLabirinto(this, EventArgs.Empty);
                    }
                    break;

                case TiposSaidas.Proximo2:
                    arvore = arvore.Filhos[1];
                    break;
            }

            arvore.SalaLabirinto.WakeUp();
        }
    }
}
